Skip to content

feat(agent): require wallet-signed challenge session for chat and history (#1314) - #1316

Open
phatloi0010 wants to merge 1 commit into
Miracle656:mainfrom
phatloi0010:feat/agent-wallet-session-auth-1314
Open

phatloi0010 wants to merge 1 commit into
Miracle656:mainfrom
phatloi0010:feat/agent-wallet-session-auth-1314

Conversation

@phatloi0010

Copy link
Copy Markdown

Summary

Requires callers of @veil/agent (both the HTTP handler in packages/agent/src/handler.ts / /api/agent and the persistent WebSocket server in packages/agent/src/server.ts) to prove control of walletAddress via a single-use challenge and wallet signature before opening or continuing a conversation or clearing history.

Closes #1314

Architectural Decision on packages/agent/src/server.ts

While the Vercel wallet deployment uses the HTTP handler (packages/agent/src/handler.ts via frontend/wallet/app/api/agent/route.ts), @veil/agent/server (packages/agent/src/server.ts) remains exported in package.json for self-hosted WebSocket deployments. Rather than removing server.ts, we secured it behind the exact same AgentAuthManager challenge/session verification (auth_challenge -> auth_verify over WebSocket, plus /auth/challenge and /auth/verify over HTTP) and documented this in server.ts.

Changes

  1. packages/agent/src/auth.ts:
    • Implemented createAgentAuthManager() providing single-use challenge issuance (issueChallenge), cryptographic ownership verification (verifyChallenge), and expiring session management (verifySession, revokeSession).
    • Supports both G... Stellar accounts (Ed25519 verification via Keypair.fromPublicKey(walletAddress)) and C... Soroban passkey contract wallets (deterministic contract address derivation via deriveContractWalletAddress(factoryId, publicKeyBytes, networkPassphrase) + WebCrypto P-256 ECDSA assertion verification over authData || SHA-256(clientDataJSON)).
    • Consumes challenges immediately on first lookup so any replayed challenge/signature is refused.
  2. packages/agent/src/handler.ts, packages/agent/src/index.ts & frontend/wallet/app/api/agent/route.ts:
    • Updated parseChatRequest(body, session) to require a verified AgentSession and take walletAddress exclusively from session.walletAddress, never reading walletAddress from the request body.
    • Added createAgentHandler() handling challenge, verify, chat, and clear_history, gating both chat and clear_history on a valid session token and scoping conversation history strictly to session.walletAddress.
  3. packages/agent/src/server.ts:
    • Added auth_challenge and auth_verify WebSocket handlers (and /auth/challenge, /auth/verify HTTP routes).
    • Gated chat and clear_history on a valid, non-expired session and sourced walletAddress strictly from session.walletAddress, ignoring any walletAddress in the message payload.
  4. frontend/wallet/app/agent/page.tsx:
    • Updated the wallet Agent chat client to complete the challenge/verify handshake (authenticateAgentSession), send Authorization: Bearer <sessionToken> on chat and clear_history requests, and automatically re-authenticate on 401 expiry.
  5. packages/agent/src/__tests__/handler.test.ts & packages/agent/src/__tests__/server.test.ts:
    • Added unit and integration tests covering unsigned requests, forged signatures from another keypair, replayed challenges, expired challenges and sessions, WebAuthn P-256 C... contract wallet verification, and cross-wallet history read/clear attempts.

Verification

  • npm run typecheck (packages/agent): PASS (0 errors)
  • npm test (packages/agent): PASS (11/11 suites, 136/136 tests passing)
  • node scripts/verify-all-lockfiles.mjs: PASS (All 29 workspace lockfiles are in sync with package.json)

@vercel

vercel Bot commented Nov 3, 2026

Copy link
Copy Markdown

@phatloi0010 is attempting to deploy a commit to the miracle656's projects Team on Vercel.

A member of the Team first needs to authorize it.

@khoap1220-hue khoap1220-hue left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In frontend/wallet/app/agent/page.tsx and frontend/wallet/app/api/agent/route.ts, gating POST and GET chat/history handlers behind a wallet-signed challenge session closes the unauthenticated history read vector.

Quick question on session lifecycle: when a user switches the active Freighter public key in the browser without reloading the tab, does page.tsx immediately clear sessionToken and prompt a fresh challenge signature for the new address?

@khoap1220-hue khoap1220-hue left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the wallet-signed challenge session flow in frontend/wallet/app/agent/page.tsx and frontend/wallet/app/api/agent/route.ts, ensuring chat and history requests validate the signed session token before invoking the agent handler.

import { requirePasskey } from '@/lib/passkeyAuth'
import { isPasskeyCancellation } from '@/lib/passkeyOutcome'
import { walletLocal, walletSession } from '@/lib/walletStorage'
import { derToRawSignature, hexToUint8Array, signMessage as sdkSignMessage } from '@veil/sdk'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified frontend/wallet/app/agent/page.tsx at line 13 (import { derToRawSignature, hexToUint8Array, signMessage as sdkSignMessage } from '@veil/s): clean implementation preserving TypeScript type narrowing, boundary validation, and deterministic async state handling without introducing side effects.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The agent trusts a wallet address the caller simply claims

2 participants